package com.jt;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.atomic.AtomicLong;

/**
 * 通过ConsumerController访问ProviderController
 * 跨进程API----RestTemplate
 */
@EnableFeignClients //告诉springboot启动时，扫描类所在包或子包中的类；假如接口上有@FeignClient注解描述,则对
//这样的接口创建其实现类,在实现类内部帮我们进行远程服务调用【第一步是导入依赖，第二步是该注解】
@SpringBootApplication
public class ConsumerApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class, args);
    }
    /**创建RestTemplate对象,然后基于此对象进行远程服务调用*/
    @Bean //1和2.注解描述方法并交给spring容器管理；第三方对象不能加Component注解；只能是Bean注解
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    @Bean
    @LoadBalanced  //3.这个注解描述RestTemplate对象时,系统底层会对RestTemplate对象的请求进行拦截
    public RestTemplate loadBalanceRestTemplate(){
        return new RestTemplate();
    }

    @Service //（1）链路式限流的业务类和注入返回值
    public class ConsumerService{
        @SentinelResource("doGetResource")
        public String doGetResource(){
            return "doGetResource";
        }
    }

    @RestController
    public class ConsumerController{ //内部类
        /*
        @Autowired 注解描述属性时，会告诉spring优先按属性的类型进行注入或查找。
        如果相同类型的对象存在多个，则需增添@Qualifier("bean的名字“)注解
         */
        @Autowired //依赖注入属性
        private RestTemplate restTemplate;//1.

        @Autowired
        private LoadBalancerClient loadBalancerClient;//2方式一
        @Autowired
        private RestTemplate loadBalanceRestTemplate;//3方式二
       //RestTemplate在发送请求的时候会被LoadBalancerInterceptor拦截
//        @Value("${server.port:8080}")
//        private String server;

        @Autowired
        private ConsumerService consumerService;//（2）链路限流对象

        @Value("${spring.application.name}")
        private String appName; //consumer

        //http://localhsot:8090/consumer/findById?id=10
        @GetMapping("/consumer/findById")  //Sentinel热点规则分析（重点）
        @SentinelResource("res")
        public String doFindById(@RequestParam Integer id){ //限流的热点参数配置；参数值为id
            return "resource id is "+id;//后台报异常：ParamFlowException: 10  //连续访问实现报错和恢复的热点限流

        }



        //AtomicLong 类支持线程安全的自增自减操作【演示：降级熔断的慢调用1】
        private AtomicLong atomicLong=new AtomicLong(1);
        @GetMapping("/consumer/test")
        public  String get( ) throws InterruptedException {
            //网址：http://localhost:8090/consumer/test   通过消费端访问提供端
            //1.直接调用(非负载均衡方式);手动的负载均衡设置[写死的服务端口]

            consumerService.doGetResource();//（3）实现链路限流，类名调用静态方法
            //目的：超过阈值，进行限流，后台报错 sentinel.slots.block.flow.FlowException: null
            //【演示：降级熔断的慢调用2】
            long num=atomicLong.getAndIncrement();//获取自增对象的值,然后再加1
            if(num%2==0){//模拟50%的慢调用比例
                Thread.sleep(200);//请求中断，刷新到时间恢复访问
            }
            String url="http://localhost:8082/provider/get/"+appName;
            return restTemplate.getForObject(url,String.class);//第二个参数：响应的数据类型

          /*  String url1 = "http://localhost:8080/provider/get"+appName;
            String url2 = "http://localhost:8081/provider/get"+appName;
            String  url[]  = new String[]{url1,url2};//动态数组
            int n = new Random().nextInt(url.length);//随机
            System.out.println("n="+n);
            String result  = restTemplate.getForObject(url[n], String.class);
            return  server+"get result is"+result;
            */
        }



       //2.负载均衡方式一
        @GetMapping("/consumer/test2")
        public String get2(){
            //基于loadBalancerClient方式获取服务实例
            String serviceId="sca-provider";//这个名字要在提供方的服务列表中
            ServiceInstance choose = loadBalancerClient.choose(serviceId);
            String ip=choose.getHost();
            int port=choose.getPort();
            //String url="http://"+ip+":"+port+"/provider/get/"+appName;
            String url=String.format("http://%s:%s/provider/get/%s",ip,port,appName);
            return restTemplate.getForObject(url,String.class);
        }
        //3.[推荐使用]负载均衡方式二:loadBalanceRestTemplate //动态调用提供方的网址信息
        @GetMapping("/consumer/test3")
        public String get3(){
            String serviceId="sca-provider";//相当于IP和port
            String url=String.format("http://%s/provider/get/%s",serviceId,appName);
           //底层在基于loadBalanceRestTemplate对象访问方式,会启动一个拦截器
            return loadBalanceRestTemplate.getForObject(url,String.class);
        }
    }

}
